﻿namespace MockingBird.Simulator.Framework
{
    using System;
    using System.Xml;
    using MockingBird.Common.Infrastructure.Logging;
    using MockingBird.Simulator.Framework.Configuration;
    using MockingBird.Simulator.Framework.Exceptions;

    /// <summary>
    /// Uses the operation inference strategy and config data to find the response item.
    /// </summary>
    internal class ResponseLocator
    {
        private IHandlerConfigurationManager configManager;
        private ILogger logger;
        private OperationType currentOperation;
        private XmlDocument currentRequest;

        internal ResponseLocator(IHandlerConfigurationManager targetConfigurationManager, ILogger targetLogger)
        {
            this.configManager = targetConfigurationManager;
            this.logger = targetLogger;

        }

        internal ResponseType FindResponseItemForOperation(OperationType targetOperation, XmlDocument targetRequest)
        {
            this.currentOperation = targetOperation;
            this.currentRequest = targetRequest;

            switch (currentOperation.ResponseStrategy)
            {
                case ResponseStrategyType.Default:
                    return this.GetDefaultResponse();

                case ResponseStrategyType.XPath:
                    return this.GetXpathBasedResponse();

                case ResponseStrategyType.Xslt:
                    return this.GetXsltBasedResponse();

                case ResponseStrategyType.None:
                    return null;

                default:
                    string errorMsg = string.Format("Response Strategy {0} was not set in config for the operation named {1}", currentOperation.ResponseStrategy.ToString(), currentOperation.Name);
                    ApplicationConfigurationException acx = new ApplicationConfigurationException(errorMsg);
                    logger.LogFatal(errorMsg, acx);
                    throw acx;
            }

            return null;


        }

        internal ResponseType GetDefaultResponse()
        {
            this.logger.Log("ResponseLocator:HandleDefaultResponse()");
            // Delegate the actual finding to the config manager (may bring this back later
            // as i dont think the config manager should care about all this)
            ResponseType locatedResponse = this.configManager.GetDefaultResponse(this.currentOperation);
            //if we couldnt find a response in config then throw an exception 
            if (locatedResponse == null)
            {
                ThrowException(new ApplicationConfigurationException(String.Format("No Default Response could be found in configuration corresponding to operation name : {0}. Please check that there is a DefaultResponse element defined.", this.currentOperation.Name)));
            }

            return locatedResponse;
        }

        internal ResponseType GetXpathBasedResponse()
        {
            this.logger.Log("ResponseLocator:HandleRequestXPathBasedResponse()");
            XmlDocument xBody = ExtractSoapBody(this.currentRequest);
            //Get the response object that matches the xpath. Let the HandlerConfiguration class
            //do the heavy lifting
            ResponseType locatedResponse = configManager.GetXPathResponse(this.currentOperation, xBody);

            //if we couldnt find a response in config then throw an exception 
            if (locatedResponse == null)
            {
                ThrowException(new ApplicationConfigurationException(String.Format("No Response could be found in the XPath elements of the configuration corresponding to operation name : {0}. Please check that the XPath expression is correct.", this.currentOperation.Name)));
            }

            return locatedResponse;
        }

        internal ResponseType GetXsltBasedResponse()
        {
            //placeholder method. to be removed when we move Xslt into a behavior rather than a strategy
            this.logger.Log("ResponseLocator:GetXsltBasedResponse()");
            ResponseType locatedResponse = this.configManager.GetDefaultResponse(this.currentOperation);
            //if we couldnt find a response in config then throw an exception 
            if (locatedResponse == null)
            {
                ThrowException(new ApplicationConfigurationException(String.Format("No XSLT Response could be found in configuration corresponding to operation name : {0}. Please check that there is a XsltResponse element defined.", this.currentOperation.Name)));
            }

            return locatedResponse;


        }


        private static XmlDocument ExtractSoapBody(XmlDocument soapRequest)
        {
            XmlDocument result = new XmlDocument();

            // TODO: this needs to be refactored so that request message handling
            // is encapsulated. The issue here is that in an ordinary ASMX based
            // soap message, the whole envelope is available whereas in the
            // WCF implementation only the internals of the body node are present.
            // For proper future processing we should be using the Message class.
            XmlNodeList xn = soapRequest.SelectNodes("//*[local-name()='Body']");
            if (xn.Count > 0)
            {
                result.LoadXml(xn[0].OuterXml);
            }
            else
            {
                result.LoadXml(soapRequest.OuterXml);
            }

            return result;
        }


        private void ThrowException(Exception e)
        {
            ApplicationOperationException opEx;

            this.logger.LogError(e.Message, e);
            System.Diagnostics.Debug.WriteLine(e.GetBaseException());

            // check the type of exception and wrap it if necessary
            switch (e.GetBaseException().GetType().Name)
            {
                case "ApplicationOperationException":
                case "ApplicationConfigurationException":
                case "HandlerConfigurationException":
                    throw e;
                default:
                    opEx = new ApplicationOperationException(e, e.Message);
                    throw opEx;
            }
        }

    }
}
